package com.ly.task;

import com.ly.exception.SmsException;
import com.ly.mapper.*;
import com.ly.po.CoursesPO;
import com.ly.po.DraftPO;
import com.ly.pojo.*;
import com.ly.service.LeaderboardService;
import com.ly.service.RedisService;
import com.ly.util.Constant;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.ly.util.TemplateUtil.renderTemplate;

@Component
public class ScheduledTasks {

    @Autowired
    private RedisService redisService;

    @Autowired
    private AnnouncementMapper announcementMapper;

    @Autowired
    private NotifyTimeMapper notifyTimeMapper;

    @Autowired
    private TeachersMapper teachersMapper;

    @Autowired
    private SmsTemplateMapper smsTemplateMapper;

    @Autowired
    private LeaderboardService leaderboardService;

    @Autowired
    private SeckillTasksMapper seckillTasksMapper;

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private CoursesMapper coursesMapper;

    @Autowired
    private StudentsMapper studentsMapper;

    @Autowired
    private SelectionsMapper selectionsMapper;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;


    //测试：每一分钟执行一次
    @Scheduled(cron = "0 */1 * * * ?")
    public void executeEveryOneMinutes() {
        System.out.println("定时扫描每一分钟：{结束考试}：测试");
       updateExamStatus();

        // 扫描全部非必修课程，找到已经结束的课程更新学分
        System.out.println("定时扫描每一分钟：{结束非必修课程}");
        updateNoRequireCredit();
    }

    // 每5分钟执行一次
    @Scheduled(cron = "0 */5 * * * ?")
    public void executeEveryFiveMinutes() {
        System.out.println("定时扫描：{发送教师短信；清除垃圾数据；更行排行榜缓存}");
        //做分钟循环检查草稿箱公告决定是否发送短信(给教师)（根据预计发布时间提前发布 5 - 120 min 不等）

        //针对学生来说，是发布时进行立即通知
        System.out.println("定时扫描每五分钟：{发送短息通知学生}");
        sendSms();

        //清除notify_time表（设置提前通知时间）的旧数据,根据draft_id去查找announcement表拿到publish_time对比现在，若现在超过当时，删除
        System.out.println("定时扫描每五分钟：{清除垃圾数据}");
        clearNotifyTime();

        //定时更新缓存中的排行榜数据
        System.out.println("定时扫描每五分钟：{更新排行榜缓存}");
        leaderboardService.addLeaderboardData();
    }

    //每天00点执行一次
    @Scheduled(cron = "0 0 0 * * ?")
    //@Scheduled(cron = "0 */1 * * * ?")
    public void executeEveryOneDay() {
        //公告过期
        //扫描公告表，对比已发布公告的publish_time和现在时间对比，超过一年的删除
        System.out.println("定时扫描每天0点：{删除过期公告表}");
        clearPublishAnnouncement();

        // 扫描exam表的所有考试安排，找到考试时间已过，且考试状态为1的记录，修改状态为3
        System.out.println("定时扫描每天0点：{结束考试}");
        updateExamStatus();

        // 扫描全部非必修课程，找到已经结束的课程更新学分
        System.out.println("定时扫描每天0点：{结束非必修课程}");
        updateNoRequireCredit();

    }

    /**
     * 更新非必修课的学分到对应学生的学分字段
     */
    private void updateNoRequireCredit() {
        // 要扫描全部的非必修的课程的class_time,week_range,semester
        // 注意9月1日到3月1日是秋季学期，其余是春季学期
        // 计算出课程的结束时间，通过学期、周次，上课时间来得到结束日期
        // 最后比较当前日期与description字段，决定是否更新该课程对应的学生的学分字段，然后在课程表中的description字段维护一个“已结课”
        List<CoursesPO> coursesPOList = coursesMapper.selectCourseByIsSeckill();
        for (CoursesPO coursesPO : coursesPOList) {
            String semester = coursesPO.getSemester();
            // 2025年春季学期,2025年秋季学期
            String weekRange = coursesPO.getWeekRange();
            // 1-16周,5-8周
            String classTime = coursesPO.getClassTime();
            // 每周一，第3、4节    每周四，第7、8节
            /*
            8:00-9:40   第1、2节课
            10:00-11:40 第3、4节课
            13:30-15:10 第5、6节课
            15:30-17:10 第7、8节课
            18:30-20:10 第9、10节课
             */
            String description = coursesPO.getDescription();
            // 计算得到结束日期
            Date endDate = null;

            try {
                // 解析学期信息
                int year = Integer.parseInt(semester.substring(0, 4));
                String semesterType = semester.substring(5, 7); // "春" 或 "秋"

                // 确定学期开始日期
                Calendar cal = Calendar.getInstance();
                if ("春季".equals(semesterType)) {
                    // 春季学期从3月1日开始
                    cal.set(year, Calendar.MARCH, 1, 0, 0, 0);
                } else {
                    // 秋季学期从9月1日开始
                    cal.set(year, Calendar.SEPTEMBER, 1, 0, 0, 0);
                }

                // 解析周次范围
                String[] weekParts = weekRange.replace("周", "").split("-");
                int endWeek = Integer.parseInt(weekParts[1]);

                // 解析上课时间和星期几
                int dayOfWeek = 1;
                if (classTime.contains("一")) dayOfWeek = 1;
                else if (classTime.contains("二")) dayOfWeek = 2;
                else if (classTime.contains("三")) dayOfWeek = 3;
                else if (classTime.contains("四")) dayOfWeek = 4;
                else if (classTime.contains("五")) dayOfWeek = 5;
                else if (classTime.contains("六")) dayOfWeek = 6;
                else if (classTime.contains("日") || classTime.contains("天")) dayOfWeek = 7;

                // 解析节次
                int lastPeriod = 0;
                java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("第([\\d、]+)节");
                java.util.regex.Matcher matcher = pattern.matcher(classTime);
                if (matcher.find()) {
                    String periodsStr = matcher.group(1);
                    String[] periodArray = periodsStr.split("、");
                    for (String period : periodArray) {
                        int p = Integer.parseInt(period);
                        if (p > lastPeriod) lastPeriod = p;
                    }
                }

                // 计算结束日期：学期开始日期 + (结束周数-1)周 + 星期几偏移
                cal.add(Calendar.WEEK_OF_YEAR, endWeek - 1);

                // 设置到指定的星期几
                int targetDayOfWeek = 0;
                switch (dayOfWeek) {
                    case 1: targetDayOfWeek = Calendar.MONDAY; break;
                    case 2: targetDayOfWeek = Calendar.TUESDAY; break;
                    case 3: targetDayOfWeek = Calendar.WEDNESDAY; break;
                    case 4: targetDayOfWeek = Calendar.THURSDAY; break;
                    case 5: targetDayOfWeek = Calendar.FRIDAY; break;
                    case 6: targetDayOfWeek = Calendar.SATURDAY; break;
                    case 7: targetDayOfWeek = Calendar.SUNDAY; break;
                }

                int currentDayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
                int diff = targetDayOfWeek - currentDayOfWeek;
                if (diff < 0) {
                    diff += 7;
                }
                cal.add(Calendar.DAY_OF_MONTH, diff);

                // 设置具体时间（根据最后一节课的结束时间）
                switch (lastPeriod) {
                    case 1:
                    case 2:
                        cal.set(Calendar.HOUR_OF_DAY, 9);
                        cal.set(Calendar.MINUTE, 40);
                        break;
                    case 3:
                    case 4:
                        cal.set(Calendar.HOUR_OF_DAY, 11);
                        cal.set(Calendar.MINUTE, 40);
                        break;
                    case 5:
                    case 6:
                        cal.set(Calendar.HOUR_OF_DAY, 15);
                        cal.set(Calendar.MINUTE, 10);
                        break;
                    case 7:
                    case 8:
                        cal.set(Calendar.HOUR_OF_DAY, 17);
                        cal.set(Calendar.MINUTE, 10);
                        break;
                    case 9:
                    case 10:
                        cal.set(Calendar.HOUR_OF_DAY, 20);
                        cal.set(Calendar.MINUTE, 10);
                        break;
                    default:
                        cal.set(Calendar.HOUR_OF_DAY, 17);
                        cal.set(Calendar.MINUTE, 10);
                }
                cal.set(Calendar.SECOND, 0);
                cal.set(Calendar.MILLISECOND, 0);

                endDate = cal.getTime();
                //
                // System.out.println("结束日期" + endDate);

            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }

            // 拿到当前日期
            Date currentDate = new Date();
            //System.out.println("当前日期" + currentDate);

            // 如果description为空 并且 结束日期已过 则更新学分
            if ((description == null || description.isEmpty()) && endDate != null && currentDate.after(endDate)) {
                Integer courseId = coursesMapper.selectIdByCourseCode(coursesPO.getCourseCode());
                List<Integer> studentIds = selectionsMapper.selectStudentIdsByCourseId(courseId);
                for (Integer studentId : studentIds) {
                    Long sno = studentsMapper.selectSnoById(studentId);
                    studentsMapper.addCreditBySno(1.5, sno);
                }
                // 更新description字段为"已结课"
                coursesMapper.updateDescriptionByCourseCode(coursesPO.getCourseCode(), "已结课");
            }
        }
    }

    private void updateExamStatus() {
        // 查询exam表,找到已过期但是考试状态还是正常的数据记录
        //List<Exam> examList = examMapper.selectExpireExamByStatus(1);
        // 查询所有考试状态正常的数据
        List<Exam> examList = examMapper.selectByStatus(1);
        for (Exam exam : examList) {
            if (new Date().after(exam.getEndTime())) {
                examMapper.updateExamStatusByExpire(exam);
            }
        }
    }

    private void sendSms() {
        //判断是否开启自动短信通知功能
        if (redisService.getValue(Constant.SWITCH_SMS).equals("false")) {
            return;
        }

        //扫描草稿箱公告，拿到所有预计发布时间在此时之后的草稿箱公告
        List<DraftPO> draftPOS = announcementMapper.selectDraftByTimeByNoAdmin();
        for (DraftPO draftPO : draftPOS) {
            // 先判断发布人是谁，如果是admin，则不去发送短信（管理员不需要）
            if (draftPO.getPublicName().equals("admin")) {
                continue;
            }

            //还应该判断当前教师是否填写了手机号，若没有填写，也不去发送（不知道往哪里发送）
            // 有BUG不是很严重先不修，先根据教师名称查找吧，警醒：注意使用唯一标识比如说教师工号
            Long phone = teachersMapper.selectPhoneByName(draftPO.getPublicName());
            if (phone == null) {
                continue;
            }

            Calendar calendar = Calendar.getInstance();
            calendar.setTime(draftPO.getPublishTime());
            calendar.add(Calendar.MINUTE, -draftPO.getTime()); // 减去n分钟
            Date newDate = calendar.getTime();


            //有一个问题：发送过的不再发送，可以同时比较 公告id + 手机号 ，存入缓存，2小时过期（120min）

            //判断是否近期发送过（两个小时）
            if (redisService.getValue(Constant.SMS_REPEAT + draftPO.getId() + "_" + phone) != null) {
                //说明重复
                continue;
            }

            if (newDate.before(new Date())) {
                //编辑内容，结合模板
                SmsTemplate smsTemplate = smsTemplateMapper.selectByTypeByStatus("TeacherTemplate", 1);
                //System.out.println(smsTemplate);
                String component = renderTemplate(smsTemplate.getComponent(), draftPO.getPublicName(), draftPO.getPublishTime());
                //System.out.println("短信内容：" + component);

                //发送短信
                Sms sms = new Sms();
                sms.setSender(Constant.PHONE_SCHOOLE);
                sms.setReceiver(phone);
                sms.setType(2);
                sms.setStatus(0);
                sms.setRead(0);
                sms.setContent(component);

                String topic = Constant.TOPIC_SMS;

                Message<Sms> message = MessageBuilder
                        .withPayload(sms)
                        .setHeader("KEYS", "sms_key_" + sms.getReceiver()) // 设置业务Key（非常重要！）
                        .build();

                try {
                    // 使用同步发送确保重要消息不丢失，并获取结果
                    SendResult sendResult = rocketMQTemplate.syncSend(topic, message);
                    System.out.println("教师短信消息发送成功。MsgId: {" + sendResult.getMsgId() + "}, Key: {" + "sms_key_" + sms.getReceiver() + "}");
                } catch (Exception e) {
                    System.out.println("教师短信消息发送失败。Key: {" + "sms_key_" + sms.getReceiver() + "}," + e);
                    // 这里可以加入重试逻辑或降级处理
                    // 发送失败就抛一个短信发送失败异常
                    throw new SmsException();
                }

                //记录入缓存，公告id + 手机号 ， 过期时间 2h ， 在发送前检查
                redisService.setValueEx(Constant.SMS_REPEAT + draftPO.getId() + "_" + phone, "1", 2, TimeUnit.HOURS);
            }
        }
    }

    private void clearNotifyTime() {
        System.out.println("定时清理冗余数据");
        List<NotifyTime> notifyTimeList = notifyTimeMapper.selectAll();
        for(NotifyTime notifyTime : notifyTimeList) {
            //对每一个notifyTime进行检查
            Announcement announcement = announcementMapper.selectByPrimaryKey(notifyTime.getDraftId());
            //留五分钟容错
            Calendar calendar = Calendar.getInstance();
            calendar.add(Calendar.MINUTE, 5); // 加 5 分钟
            Date currentTimePlus5Minutes = calendar.getTime();
            if (announcement.getPublishTime().before(currentTimePlus5Minutes)){
                //删除
                notifyTimeMapper.deleteByDraftId(announcement.getId());
            }
        }
    }

    private void clearPublishAnnouncement() {
        //拿到已发布公告
        List<Announcement> announcementList = announcementMapper.selectPublished();

        //遍历已发布公告，对比时间
        for (Announcement announcement : announcementList) {
            if (isExpire(announcement.getPublishTime())) {
                //早于一年前，过期，删除
                announcementMapper.deleteByPrimaryKey(announcement.getId());
            }
        }
    }

    private boolean isExpire(Date publishTime) {
        if (publishTime == null) {
            return true; // 或者根据业务需求返回相应值
        }

        // 将Date转换为LocalDateTime
        LocalDateTime publishDateTime = publishTime.toInstant()
                .atZone(ZoneId.systemDefault())
                .toLocalDateTime();

        // 获取一年前的当前时间
        LocalDateTime oneYearAgo = LocalDateTime.now().minusYears(1);

        // 比较发布时间是否早于一年前
        return publishDateTime.isBefore(oneYearAgo);
    }

}
